package com.netsdk.demo.customize;

import static com.netsdk.lib.Utils.getOsPrefix;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Objects;
import java.util.Scanner;

import com.netsdk.demo.util.CaseMenu;
import com.netsdk.lib.NetSDKLib;
import com.netsdk.lib.NetSDKLib.*;
import com.netsdk.lib.enumeration.EM_EVENT_IVS_TYPE;
import com.netsdk.lib.structure.CFG_INTELLI_SHELF_INFO;
import com.netsdk.lib.structure.CFG_IVS_MAN_NUM_DETECTION_INFO;
import com.netsdk.lib.structure.CFG_NUMBERSTAT_INFO;
import com.netsdk.lib.structure.DEV_EVENT_INTELLI_SHELF_INFO;
import com.netsdk.lib.ToolKits;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;


/**
 * 视频统计摘要信息
 * 进出数量统计
 * @author 29779
 *
 */
public class VideoStatistic {
	
	static NetSDKLib netsdkApi 	= NetSDKLib.NETSDK_INSTANCE;
	static NetSDKLib configApi 	= NetSDKLib.CONFIG_INSTANCE;
	
	private NetSDKLib.NET_DEVICEINFO_Ex deviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex();
	// 登录句柄
	private static LLong loginHandle = new LLong(0);
	
    public static String encode;
	
    static {
        String osPrefix = getOsPrefix();
        if (osPrefix.toLowerCase().startsWith("win32")) {
            encode = "GBK";
        } else if (osPrefix.toLowerCase().startsWith("linux")) {
            encode = "UTF-8";
        }
    }
	// 智能订阅句柄
	private LLong attachHandle = new NetSDKLib.LLong(0);

	String address 			= "172.27.1.173";
	int    port 			= 37777;
	String username 		= "admin";
	String password 		= "admin123";
	//
	
	private static class DisconnectCallback implements NetSDKLib.fDisConnect {
		private static DisconnectCallback instance = new DisconnectCallback();
		private DisconnectCallback() {}
		public static DisconnectCallback getInstance() { 
			return instance;
		}
		
		@Override
		public void invoke(LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser){
			System.out.printf("Device[%s:%d] Disconnect!\n" , pchDVRIP , nDVRPort);
		}
    }
	
	private static class HaveReconnectCallback implements NetSDKLib.fHaveReConnect {
		private static HaveReconnectCallback instance = new HaveReconnectCallback();
		private HaveReconnectCallback() {}
		public static HaveReconnectCallback getInstance() { 
			return instance;
		}
		
		@Override
		public void invoke(LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser){
			System.out.printf("Device[%s:%d] HaveReconnected!\n" , pchDVRIP , nDVRPort);
		}
	}	


	

	public void EndTest() {
		System.out.println("End Test");
		if( loginHandle.longValue() != 0) {
			netsdkApi.CLIENT_Logout(loginHandle);
		}
		System.out.println("See You...");
		
		netsdkApi.CLIENT_Cleanup(); // 进程关闭时调用一次
		System.exit(0);
	}

	public void InitTest() {
		//初始化SDK库
		netsdkApi.CLIENT_Init(DisconnectCallback.getInstance(), null); // 进程启动时调用一次
		
		//设置断线自动重练功能
		netsdkApi.CLIENT_SetAutoReconnect(HaveReconnectCallback.getInstance(), null);
    	
    	// 向设备登入
    	final int nSpecCap = 0; /// login device by TCP
    	final IntByReference error = new IntByReference();
    	
    	loginHandle = netsdkApi.CLIENT_LoginEx2(address, (short)port, username,
				password, nSpecCap, null, deviceInfo, error);
		
    	if(loginHandle.longValue() == 0) {
    		System.err.printf("Login Device [%s:%d] Failed ! Last Error[%x]\n", address, port, netsdkApi.CLIENT_GetLastError());
    		return;
    	}

    	System.out.printf("Login Device [%s:%d] Success. \n", address, port);	
	}
	
	private static class VideoStatSumCallback implements NetSDKLib.fVideoStatSumCallBack {
		private static VideoStatSumCallback instance = new VideoStatSumCallback();
		private VideoStatSumCallback() {}
		public static VideoStatSumCallback getInstance() { 
			return instance;
		}
		
	  	@Override
		public void invoke(LLong lAttachHandle, NET_VIDEOSTAT_SUMMARY stVideoState, int dwBufLen, Pointer dwUser){
			/**
			 * 回调函数使用注意（防止卡回调）
			 * 1.不能再回调进行耗时操作，建议将数据、图片流、视频流等通过中间件和多线程抛出处理
			 * 2.不能再回调调用netsdk的其他接口
			 */	  			  		
	  		System.out.printf("Channel[%d] GetTime[%s]\n" +
	  				"People In  Information[Total[%d] Hour[%d] Today[%d]]\n" +
	  				"People Out Information[Total[%d] Hour[%d] Today[%d]]\n", 
	  				stVideoState.nChannelID , stVideoState.stuTime.toStringTime() , 
	  				stVideoState.stuEnteredSubtotal.nToday , 
	  				stVideoState.stuEnteredSubtotal.nHour , 
	  				stVideoState.stuEnteredSubtotal.nTotal , 
	  				stVideoState.stuExitedSubtotal.nToday , 
	  				stVideoState.stuExitedSubtotal.nHour , 
	  				stVideoState.stuExitedSubtotal.nTotal );
	  		
	  		try {	  			
	  			/**
	  			 * 方案:通过上报的规则类型名称 和区域ID值 去智能规则配置关系获取自定义的规则名称
	  			 * 一.规则类型名称
				 * 输出字符串 
				 * 1."ManNumDetection" 对应 智能规则配置枚举 EVENT_IVS_MAN_NUM_DETECTION(立体视觉区域内人数统计事件)
				 * 2."NumberStat"   对应 智能规则配置枚举 EVENT_IVS_NUMBERSTAT(数量统计事件)对应的规则配置
				 * 
				 * 二、区域ID
				 * 输出区域ID值  对应智能规则字段nAreaID的值
	  			 */	  											
				System.out.println("规则类型名称:"+new String(stVideoState.szRuleName,encode)); 
				System.out.println("区域ID:"+stVideoState.nAreaID);												
				
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	
	/**
	 * 查询智能规则配置信息
	 */
	public void getCongigRuleInfo() {
		int channel = 0; // 通道号
		String command = NetSDKLib.CFG_CMD_ANALYSERULE;

		int ruleCount = 10; // 事件规则个数
		CFG_RULE_INFO[] ruleInfo = new CFG_RULE_INFO[ruleCount];
		for (int i = 0; i < ruleCount; i++) {
			ruleInfo[i] = new CFG_RULE_INFO();
		}

		CFG_ANALYSERULES_INFO analyse = new CFG_ANALYSERULES_INFO();
		analyse.nRuleLen = 1024 * 1024 * 40;
		analyse.pRuleBuf = new Memory(1024 * 1024 * 40); // 申请内存
		analyse.pRuleBuf.clear(1024 * 1024 * 40);

		// 查询
		if (ToolKits.GetDevConfig(loginHandle, channel, command, analyse)) {
			int offset = 0;
			System.out.println("设备返回的事件规则个数:" + analyse.nRuleCount);
			int count = analyse.nRuleCount < ruleCount ? analyse.nRuleCount : ruleCount;
			for (int i = 0; i < count; i++) {
				// 每个视频输入通道对应的所有事件规则：缓冲区pRuleBuf填充多个事件规则信息，每个事件规则信息内容为 CFG_RULE_INFO + 事件类型对应的规则配置结构体。
				ToolKits.GetPointerDataToStruct(analyse.pRuleBuf, offset, ruleInfo[i]);

				offset += ruleInfo[0].size(); // 智能规则偏移量

				switch (ruleInfo[i].dwRuleType) {
				case NetSDKLib.EVENT_IVS_CROSSLINEDETECTION: { //事件类型 EVENT_IVS_CROSSLINEDETECTION (警戒线事件)对应的规则配置CFG_CROSSLINE_INFO
					//System.out.println("警戒线配置----------------------------");
					break;
				}
				case NetSDKLib.EVENT_IVS_CROSSREGIONDETECTION: {//事件类型EVENT_IVS_CROSSREGIONDETECTION(警戒区事件)对应的规则配置CFG_CROSSREGION_INFO
					//System.out.println("警戒区配置-----------------------------");
					break;
				}
				case NetSDKLib.EVENT_IVS_NUMBERSTAT: {  //事件类型EVENT_IVS_NUMBERSTAT(数量统计事件)对应的规则配置 CFG_NUMBERSTAT_INFO
					// #define EVENT_IVS_NUMBERSTAT   = 0x00000010 					
					System.out.println("数量统计-----------------------------");
					System.out.println("数量统计的枚举值："+NetSDKLib.EVENT_IVS_NUMBERSTAT+" 对应规则类型名称字符串'NumberStat'");
					CFG_NUMBERSTAT_INFO msg = new CFG_NUMBERSTAT_INFO();
					ToolKits.GetPointerDataToStruct(analyse.pRuleBuf, offset, msg);
					try {
						System.out.println("自定义规则名称:"+ new String(msg.szRuleName,encode)); // 用户需要的名称
						System.out.println("区域ID:"+ msg.nAreaID);	
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					}
					break;
				}
				case NetSDKLib.EVENT_IVS_MAN_NUM_DETECTION: { //事件类型EVENT_IVS_MAN_NUM_DETECTION(立体视觉区域内人数统计事件)对应的规则配置 CFG_IVS_MAN_NUM_DETECTION_INFO
					//#define EVENT_IVS_MAN_NUM_DETECTION  = 0x0000020E  
					System.out.println("区域内人数统计-----------------------------");
					System.out.println("区域内人数统计的枚举值："+NetSDKLib.EVENT_IVS_MAN_NUM_DETECTION+" 对应规则类型名称字符串'ManNumDetection'");
					CFG_IVS_MAN_NUM_DETECTION_INFO msg = new CFG_IVS_MAN_NUM_DETECTION_INFO();
					ToolKits.GetPointerDataToStruct(analyse.pRuleBuf, offset, msg);
					try {
						System.out.println("自定义规则名称:"+ new String(msg.szRuleName,encode)); // 用户需要的名称
						System.out.println("区域ID:"+ msg.nAreaID);	
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					}
					break;
				}
				case NetSDKLib.EVENT_IVS_INTELLI_SHELF: { //事件类型EVENT_IVS_INTELLI_SHELF(智能补货事件)对应的规则配置 CFG_INTELLI_SHELF_INFO
					//#define EVENT_IVS_INTELLI_SHELF  = 0x00000277 
					System.out.println("智能补货事件-----------------------------");
					System.out.println("智能补货事件的枚举值："+NetSDKLib.EVENT_IVS_INTELLI_SHELF+" 对应规则类型名称字符串'IntelliShelf'");
					CFG_INTELLI_SHELF_INFO msg = new CFG_INTELLI_SHELF_INFO();
					ToolKits.GetPointerDataToStruct(analyse.pRuleBuf, offset, msg);
					try {
						System.out.println("自定义规则名称:"+ new String(msg.szRuleName,encode)); // 用户需要的名称
						System.out.println("区域ID:"+ msg.nAreaID);	
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					}
					break;
				}
				default:
					break;
				}
				offset += ruleInfo[i].nRuleSize; // 智能事件偏移量
			}
		} else {
			System.err.println("查询智能事件规则信息失败!" + ToolKits.getErrorCode());
		}
	}
	
	

	/** 订阅视频统计 句柄*/
	private LLong videoStatHandle = new LLong(0);
	
	/**
	 * 订阅
	 */
	public void attachVideoStatSummary() {
		if (loginHandle.longValue() == 0) {
			return;
		}
		
		NET_IN_ATTACH_VIDEOSTAT_SUM videoStatIn = new  NET_IN_ATTACH_VIDEOSTAT_SUM();
		videoStatIn.nChannel = 0;
		videoStatIn.cbVideoStatSum = VideoStatSumCallback.getInstance();
		
		NET_OUT_ATTACH_VIDEOSTAT_SUM videoStatOut = new NET_OUT_ATTACH_VIDEOSTAT_SUM();
		
		videoStatHandle = netsdkApi.CLIENT_AttachVideoStatSummary(loginHandle, videoStatIn, videoStatOut, 5000);
		if( videoStatHandle.longValue() == 0 ) {
			System.err.printf("Attach Failed!LastError = %x\n",  netsdkApi.CLIENT_GetLastError());
			return;
		}

		System.out.printf("Attach Success!Wait Device Notify Information\n");
	}
	
	/**
	 * 退订
	 */
	public void detachVideoStatSummary() {
		if (videoStatHandle.longValue() != 0) {
			netsdkApi.CLIENT_DetachVideoStatSummary(videoStatHandle);
			videoStatHandle.setValue(0);
		}
	}
	
	private LLong findHandle = new LLong(0); // 查询句柄
	 
	public void startFindNumberStat() {
		NetSDKLib.NET_IN_FINDNUMBERSTAT inParam = new NetSDKLib.NET_IN_FINDNUMBERSTAT();
		inParam.nChannelID = 0; // 通道号
		
		inParam.stStartTime.dwYear = 2016;// 开始时间
		inParam.stStartTime.dwMonth = 6;
		inParam.stStartTime.dwDay = 20;
		inParam.stStartTime.dwHour = 0;

		inParam.stEndTime.dwYear = 2016;// 结束时间
		inParam.stEndTime.dwMonth = 6;
		inParam.stEndTime.dwDay = 21;
		inParam.stEndTime.dwHour = 23;
					
		inParam.nGranularityType = 1;// 颗粒度				
		inParam.nWaittime = 5000; // 接口超时时间5s
		
		NetSDKLib.NET_OUT_FINDNUMBERSTAT outParam = new NetSDKLib.NET_OUT_FINDNUMBERSTAT();
		
		/// 获取查询句柄
		findHandle = netsdkApi.CLIENT_StartFindNumberStat(loginHandle, inParam, outParam);
		if (findHandle.longValue() == 0) {
			System.err.printf("StartFindNumberStat Failed! LastError = 0x%x\n", netsdkApi.CLIENT_GetLastError() );
			return;
		}
		
		System.out.println("dwTotalCount: " + outParam.dwTotalCount);			
		/// 查询
		NetSDKLib.NET_IN_DOFINDNUMBERSTAT inDoFind = new NetSDKLib.NET_IN_DOFINDNUMBERSTAT();
		inDoFind.nBeginNumber = 0; // 从0开始查询
		inDoFind.nCount = 10; // 每次查询10条
		inDoFind.nWaittime = 5000; //接口超时时间 5s
		
		NetSDKLib.NET_OUT_DOFINDNUMBERSTAT outDofind = new NetSDKLib.NET_OUT_DOFINDNUMBERSTAT();
		NetSDKLib.NET_NUMBERSTAT[] numberstat = (NetSDKLib.NET_NUMBERSTAT[])new NetSDKLib.NET_NUMBERSTAT().toArray(inDoFind.nCount);
		
		for (int i = 0 ;i < numberstat.length; i ++) {
			numberstat[i].dwSize = numberstat[0].dwSize;
		}
		
		outDofind.nBufferLen = inDoFind.nCount * numberstat[0].dwSize;
		outDofind.pstuNumberStat = new Memory(outDofind.nBufferLen);
		outDofind.pstuNumberStat.clear(outDofind.nBufferLen);
		// Note： 初始化内存中 dwSize 字段的值，否则查询的结果为空
		ToolKits.SetStructArrToPointerData(numberstat, outDofind.pstuNumberStat);
		
		int bFound = 0;
		int index = 1;
		do {
			bFound = netsdkApi.CLIENT_DoFindNumberStat(findHandle, inDoFind, outDofind);
			if (0 == bFound) {
				System.err.printf("DoFindNumberStat Failed! LastError = 0x%x\n", netsdkApi.CLIENT_GetLastError() );
				return;
			}
			
			// 查询结果
			ToolKits.GetPointerDataToStructArr(outDofind.pstuNumberStat, numberstat);
			for(int i = 0; i < outDofind.nCount; i++, index ++) {
				System.out.println("Index = " + index +
						" Start Time: " + numberstat[i].stuStartTime.toStringTime() +
						" Stop Time: " + numberstat[i].stuEndTime.toStringTime() +
						" Enter Total: " + numberstat[i].nEnteredSubTotal +
						" Exit Total: " + numberstat[i].nExitedSubtotal + "\n"
						);
			}
			
			// 查询下一次
			inDoFind.nBeginNumber += inDoFind.nCount; // 从上一次结束地方开始查询
		}while(outDofind.nCount >= inDoFind.nCount);
	}
	
	public void stopFindNumberStat() {
		if (findHandle.longValue() != 0 ) {
			netsdkApi.CLIENT_StopFindNumberStat(findHandle);
			System.out.println("stop Find Number Stat Success. ");
			findHandle.setValue(0);
		}
	}
	
	
	
	/**
	 * 选择通道
	 */
	private int channel = 0;

	public void setChannelID() {
		System.out.println("请输入通道，从0开始计数，-1表示全部");
		Scanner sc = new Scanner(System.in);
		this.channel = sc.nextInt();
	}

	/**
	 * 订阅智能事件
	 */
	public void AttachEventRealLoadPic() {
		// 先退订，设备不会对重复订阅作校验，重复订阅后会有重复的事件返回
		this.DetachEventRealLoadPic();
		// 需要图片
		int bNeedPicture = 1;
		attachHandle = netsdkApi.CLIENT_RealLoadPictureEx(loginHandle, channel, NetSDKLib.EVENT_IVS_ALL, bNeedPicture,
				AnalyzerDataCB.getInstance(), null, null);
		if (attachHandle.longValue() != 0) {
			System.out.printf("Chn[%d] CLIENT_RealLoadPictureEx Success\n", channel);
		} else {
			System.out.printf("Ch[%d] CLIENT_RealLoadPictureEx Failed!LastError = %s\n", channel,
					ToolKits.getErrorCode());
		}
	}

	/**
	 * 智能事件回调
	 */
	private static class AnalyzerDataCB implements NetSDKLib.fAnalyzerDataCallBack {
		private final File picturePath;
		private static AnalyzerDataCB instance;

		private AnalyzerDataCB() {
			picturePath = new File("./AnalyzerPicture/");
			if (!picturePath.exists()) {
				picturePath.mkdirs();
			}
		}

		public static AnalyzerDataCB getInstance() {
			if (instance == null) {
				synchronized (AnalyzerDataCB.class) {
					if (instance == null) {
						instance = new AnalyzerDataCB();
					}
				}
			}
			return instance;
		}

		@Override
		public int invoke(LLong lAnalyzerHandle, int dwAlarmType, Pointer pAlarmInfo, Pointer pBuffer, int dwBufSize,
				Pointer dwUser, int nSequence, Pointer reserved) {
			if (lAnalyzerHandle == null || lAnalyzerHandle.longValue() == 0) {
				return -1;
			}
			System.out.println("dwAlarmType:"+dwAlarmType);
			switch (Objects.requireNonNull(EM_EVENT_IVS_TYPE.getEventType(dwAlarmType))) {
			case EVENT_IVS_INTELLI_SHELF: {// 智能补货事件(对应 DEV_EVENT_INTELLI_SHELF_INFO)
				DEV_EVENT_INTELLI_SHELF_INFO msg = new DEV_EVENT_INTELLI_SHELF_INFO();
				ToolKits.GetPointerData(pAlarmInfo, msg);				
				System.out.println(" 智能补货事件 时间(UTC)：" + msg.UTC 
						+ " 通道号:" + msg.nChannelID
				        +" 事件ID:" + msg.nEventID
						+" 事件名称:" +new String(msg.szName).trim() // 规则类型名称
						+" 区域ID（从1开始）, 0为单区域:" +msg.nAreaID  // 区域ID
						+" 事件触发的预置点号，从1开始:" +msg.nPresetID
						+" 表示货架上剩余货物的百分比:" +msg.nPercent
						);
				/**
				 * 回调函数使用注意（防止卡回调）
				 * 1.不能再回调进行耗时操作，建议将数据、图片流、视频流等通过中间件和多线程抛出处理
				 * 2.不能再回调调用netsdk的其他接口
				 */
				// 全图
				String Picture = picturePath + "\\" + "EVENT_IVS_INTELLI_SHELF" + System.currentTimeMillis()
						+ ".jpg";
				if (dwBufSize > 0) {
					ToolKits.savePicture(pBuffer, 0, dwBufSize, Picture);
				}
				// 全景大图
				if (msg.stuSceneImage != null && msg.stuSceneImage.nLength > 0) {
					String bigPicture = picturePath + "\\" + "big" + System.currentTimeMillis()
							+ ".jpg";
					ToolKits.savePicture(pBuffer, msg.stuSceneImage.nOffSet, msg.stuSceneImage.nLength, bigPicture);
				}
				
				break;
			}			
			default:
				System.out.println("其他事件--------------------" + dwAlarmType);
				break;
			}
			return 0;
		}

	}

	/**
	 * 停止侦听智能事件
	 */
	public void DetachEventRealLoadPic() {
		if (attachHandle.longValue() != 0) {
			netsdkApi.CLIENT_StopLoadPic(attachHandle);
		}
	}


	public void RunTest() {
		System.out.println("Run Test");
		
		CaseMenu menu = new CaseMenu();		
		menu.addItem(new CaseMenu.Item(this, "查询智能规则配置信息", "getCongigRuleInfo"));
		menu.addItem(new CaseMenu.Item(this, "订阅", "attachVideoStatSummary"));
		menu.addItem(new CaseMenu.Item(this, "退订", "detachVideoStatSummary"));		
		
		//menu.addItem(new CaseMenu.Item(this, "开始查询", "startFindNumberStat"));
		//menu.addItem(new CaseMenu.Item(this, "停止查询", "stopFindNumberStat"));
		
		menu.addItem(new CaseMenu.Item(this, "选择通道", "setChannelID"));
		menu.addItem(new CaseMenu.Item(this, "订阅智能事件", "AttachEventRealLoadPic"));
		menu.addItem(new CaseMenu.Item(this, "停止侦听智能事件", "DetachEventRealLoadPic"));
		
		menu.run();
	}

	public static void main(String[]args)
	{
		VideoStatistic demo = new VideoStatistic();
		demo.InitTest();
		demo.RunTest();
		demo.EndTest();
	}
}

